package com.microservice.gateway.filter;

import com.microservice.core.constant.HttpStatus;
import com.microservice.core.constant.SecurityConstants;
import com.microservice.core.constant.TokenConstants;
import com.microservice.core.redis.service.RedisService;
import com.microservice.core.utils.JwtUtil;
import com.microservice.core.utils.ServletUtils;
import com.microservice.core.utils.StringUtils;
import com.microservice.gateway.order.CustomOrdered;
import com.microservice.gateway.properties.IgnoreWhiteProperties;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.ExpiredJwtException;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

/**
 * 网关鉴权过滤器,实现Ordered接口,是为了设置过滤器执行优先级,
 * 在请求还没有路由到具体服务时实现拦截鉴权处理
 */
@Component
@Slf4j
public class AuthFilter implements GlobalFilter, CustomOrdered {

    @Resource
    private JwtUtil jwtUtil;

    @Resource
    private IgnoreWhiteProperties ignoreWhiteProperties;

    @Resource
    private RedisService redisService;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        ServerHttpRequest request = exchange.getRequest();
        // 打印请求信息
        log.info("请求完整urI为: {}", request.getURI());
        ServerHttpRequest.Builder mutate = request.mutate();
        // 跳过白名单不需要验证的路径
        String url = request.getURI().getPath();
        if (StringUtils.matches(url, ignoreWhiteProperties.getWhites())) {
            return chain.filter(exchange);
        }
        //获取请求头的token
        String access_token = jwtUtil.getTokenReplacePrefix(request.getHeaders().getFirst(TokenConstants.AUTHENTICATION));
        if (StringUtils.isEmpty(access_token)) {
            return unauthorizedResponse(exchange, "access_token令牌不能为空");
        }
        try {
            jwtUtil.getClaimsByAccessToken(access_token);
        } catch (ExpiredJwtException e) {
            return unauthorizedResponse(exchange, "JWT令牌过期");
        }
        Claims claims = jwtUtil.getClaimsByAccessToken(access_token);
        if (claims == null) {
            return unauthorizedResponse(exchange, "JWT的token令牌已过期或验证不正确！");
        }
        //这里为什么要使用redis来控制用户的登录状态呢,
        //因为JWT本身是无状态的，设置了token生成的有效期后，除非自己过期，否则是无法通过JWT去手动使token失效的
        //所以需要使用redis来控制用户的登录状态,用户登录时生成token,并将token保存到redis中,设置有效期
        //当用户退出登录时，将token从redis中删除，这样当用户再次访问时，通过判断redis中是否存在该token来判断是否登录
        String userLoginUniqueKey = jwtUtil.getClaimFiled(access_token, SecurityConstants.USER_LOGIN_UNIQUE_KEY);
        boolean isLogin = redisService.hasKey(jwtUtil.getLoginUserKey(userLoginUniqueKey));
        if (!isLogin) {
            return unauthorizedResponse(exchange, "登录状态已过期");
        }
        String userId = jwtUtil.getClaimFiled(access_token, SecurityConstants.DETAILS_USER_ID);
        String userName = jwtUtil.getClaimFiled(access_token, SecurityConstants.DETAILS_USERNAME);
        log.info("用户ID:{},用户名:{},用户登录唯一标识userLoginUniqueKey:{}", userId, userName, userLoginUniqueKey);
        return chain.filter(exchange.mutate().request(mutate.build()).build());
    }

    /**
     * 鉴权失败返回结果
     */
    private Mono<Void> unauthorizedResponse(ServerWebExchange exchange, String msg) {
        log.error("[鉴权异常处理]请求路径:{},错误信息:{}", exchange.getRequest().getPath(), msg);
        return ServletUtils.webFluxResponseWriter(exchange.getResponse(), msg, HttpStatus.UNAUTHORIZED);
    }
}
